/*
  (C) 2005 Adam D. Bradley

  This program is free software; you can redistribute it and/or
  modify it under the terms of Version 2 of the GNU General Public 
  License.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.
  
  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free
  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

package http;

import http.NanoHTTPD;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Properties;

public class Server extends NanoHTTPD
{
	static String coreID = "snbench-http-server/0.20050719";
	private String serverID;

	private Server() throws IOException {
		super(8080);
		serverID = new String(coreID);
		hlist = new java.util.ArrayList<ResourceHandler>();
	}

	private Server(int port) throws IOException {
		super(port);
		serverID = new String(coreID);
		hlist = new java.util.ArrayList<ResourceHandler>();
	}

	static java.util.HashMap<Integer, Server> serverPortMap = new java.util.HashMap<Integer, Server>();

	public static Server GetServer() throws IOException {
		Server s = (Server) serverPortMap.get(new Integer(8080));
		if (s == null) {
			s = new Server(8080);
			serverPortMap.put(new Integer(8080), s);
		}
		return s;
	}

	public static Server GetServer(int port) throws IOException {
		Server s = (Server) serverPortMap.get(new Integer(port));
		if (s == null) {
			s = new Server(port);
			serverPortMap.put(new Integer(port), s);
		}
		return s;
	}

	public static Server GetServer(String swid) throws IOException {
		http.Server s = GetServer();
		s.serverID += " " + swid;
		return s;
	}
	
	public static Server GetServer(String swid, int port) throws IOException {
		http.Server s = GetServer(port);
		s.serverID += " " + swid;
		return s;
	}
	
	java.util.ArrayList<ResourceHandler> hlist;

	public void RegisterHandler(ResourceHandler rh) {
		hlist.add(rh);
	}

	/**
	 * Use this method to proxy another HTTP URL.
	 * Not HTTP/1.1-compliant, but close enough for rock-n-roll.
	 */
	public Response proxyFile(Properties _header,
				   String _uri) 
	throws java.net.MalformedURLException, java.io.IOException {
		java.net.URL url = new java.net.URL(_uri);
		java.net.HttpURLConnection conn = 
			(java.net.HttpURLConnection) url.openConnection();
		conn.connect();
		java.io.InputStream instream = conn.getInputStream();

		Response r = new Response(
			"" + conn.getResponseCode() + " " + conn.getResponseMessage(),
			conn.getContentType(),
			instream);

		if (conn.getContentEncoding() != null)
			r.addHeader("Content-Encoding", conn.getContentEncoding());
		if (conn.getContentLength() != -1)
			r.addHeader("Content-Length", "" + conn.getContentLength());
		if (conn.getHeaderField("Date") != null)
			r.addHeader("Date", conn.getHeaderField("Date"));
		if (conn.getHeaderField("Last-Modified") != null)
			r.addHeader("Last-Modified", conn.getHeaderField("Last-Modified"));
		if (conn.getHeaderField("Cache-Control") != null)
			r.addHeader("Cache-Control", conn.getHeaderField("Cache-Control"));
		return r;
	}

	public Response serve(String uri,
			      String method,
			      Properties header,
			      Properties parms,
			      byte [] body) {
		Response resp = null;
		java.util.ListIterator lit = hlist.listIterator();
		while ((resp == null) && lit.hasNext()) {
			resp = ((ResourceHandler) (lit.next())).serve(
				uri, method, header, parms, body);
		}
		if (resp == null)
			resp = NotFound();
		return resp;
	}

	public Response NoCache(Response r) {
		r.addHeader("Cache-Control", "no-cache");
		r.addHeader("Pragma", "no-cache");
		return r;
	}

	public Response OK() {
		return new Response(HTTP_OK, MIME_HTML,
				    "<html><body><h1>OK</h1></body></html>");
	}

	public Response OK(String mt, String body) {
		return new Response(HTTP_OK, mt, body);
	}

	public Response OK(String mt, ByteArrayInputStream bais) {
		return new Response(HTTP_OK, mt, bais);
	}
	
	public Response Created() {
		return new Response(
			HTTP_CREATED, MIME_HTML,
			"<html><body><h1>Created</h1></body></html>");
	}

	public Response Created(String mt, String body) {
		return new Response(HTTP_CREATED, mt, body);
	}

	public Response Accepted() {
		return new Response(
			HTTP_ACCEPTED, MIME_HTML,
			"<html><body><h1>Accepted</h1></body></html>");
	}

	public Response Accepted(String mt, String body) {
		return new Response(HTTP_ACCEPTED, mt, body);
	}

	public Response Bounce(String uri) {
		Response r = new Response(
			HTTP_REDIRECT, MIME_HTML,
			"<html><body><h1>301 Moved Permanently</h1>" + 
			"Found at <a href=\"" + uri + 
			"\">" + uri + "</a></body></html>");
		r.addHeader("Location", uri);
		return r;
	}

	public Response BadRequest(String note) {
		Response r = new Response(
			HTTP_BADREQUEST, MIME_HTML,
			"<html><body><h1>400 Bad Request</h1>" +
			note + "</body></html>");
		return r;
	}

	public Response Forbidden(String note) {
		Response r = new Response(
			HTTP_FORBIDDEN, MIME_HTML,
			"<html><body><h1>403 Forbidden</h1>" + 
			note + "</body></html>");
		return r;
	}

	public Response NotFound() {
		Response r = new Response(
			HTTP_NOTFOUND, MIME_HTML,
			"<html><body><h1>404 Not Found</h1></body></html>");
		return r;
	}

	public Response NotFound(String note) {
		Response r = new Response(
			HTTP_NOTFOUND, MIME_HTML,
			"<html><body><h1>404 Not Found</h1>" + note + "</body></html>");
		return r;
	}

	public Response MethodNotAllowed(String allow) {
		Response r = new Response(
			"405 Method Not Allowed", MIME_HTML,
			"<html><body><h1>405 Method Not Allowed</h1>" + 
			"Only " + allow + " allowed</body></html>");
		r.addHeader("Allow", allow);
		return r;
	}
	
	public Response NotAcceptable(String available) {
		Response r = new Response(
			"406 Not Acceptable", MIME_HTML,
			"<html><body><h1>406 Not Acceptable</h1>" +
			((available!=null)?
			 ("Available MIME types: " + available):
			 ("No available representation of this resource"
			  +" is acceptable to your client.")) + 
			"</body></html>");
		return r;
	}
	
	public Response UnsupportedMediaType(String note) {
		return new Response(
			"411 Unsupported Media Type", MIME_HTML,
			"<html><body><h1>411 Unsupported Media Type</h1>" + 
			note + "</body></html>");
	}

	public Response InternalServerError(String error) {
		return new Response(
			HTTP_INTERNALERROR, MIME_HTML,
			"<html><body><h1>Internal Server Error</h1>" +
			((error==null)?"Internal Server Error":error)
			+ "</body></html>");
	}

	public Response ServiceUnavailable(String note) {
		return new Response(
			"503 Service Unavailable", MIME_HTML,
			"<html><body><h1>503 Service Unavailable</h1>" + 
			note + "</body></html>");
	}


	public static Properties BuildAcceptTable(String ahdr) {
		Properties a = new Properties();
		if (ahdr == null) 
			return a;
		String[] accs = ahdr.split(",");
		for (int i=0 ; i<accs.length ; i++) {
			String[] acval = accs[i].split(";");
			String type = acval[0].trim();
			String qval = "1.0";
			if (acval.length > 1) {
				String[] acparms = acval[1].split(",");
				for (int j=0 ; j<acparms.length ; j++) {
					String z = acparms[j].trim();
					if (z.substring(0,2).equals("q=")) {
						qval = z.substring(2).trim();
						break;
					}
				}
			}
			a.setProperty(type, qval);
		}
		return a;
	}

	public static double Acceptable(Properties at, String mt) {
		String gmt = mt.replaceFirst("/.*", "/\\*");
		String qv;
		double qval;
		double max = 0.0;

		//System.err.println("Checking [" + mt + "] and [" + gmt + "]...");

		if (at.size() == 0)
			return 1.0;

		/* First, look for a direct match */
		if ((qv = at.getProperty(mt)) != null) {
			qval = Double.valueOf(qv).doubleValue();
			//System.err.println("  Got exact match: [" + qv + "]/" + 
			//		   Double.toString(qval));
			if (qval == 0.0)
				return 0.0;
			max = qval;
		}

		if ((qv = at.getProperty(gmt)) != null) {
			qval = Double.valueOf(qv).doubleValue();
			//System.err.println("  Got half match: [" + qv + "]/" + 
			//		   Double.toString(qval));
			if (qval == 0.0)
				return 0.0;
			if (qval > max)
				max = qval;
		}

		if ((qv = at.getProperty("*/*")) != null) {
			qval = Double.valueOf(qv).doubleValue();
			/* Even if someone says they want the * / * mime type,
			   they probably don't really want snbench/* types since
			   they are architecture-specific.  So we ignore all such
			   claims when an snbench/* type is in question. */
			if (mt.substring(0,8).equals("snbench/") &&
			    qval > 0.0)
				return max; /* short-circuit remaining cases... */

			// System.err.println("  Got universal match: [" + qv + "]/" + Double.toString(qval));
			if ((qval == 0.0) && (max == 0.0))
				return 0.0;
			if (qval > max)
				max = qval;
		}

		//System.err.println("  Returning " + Double.toString(max));

		return max;
	}
}
